#include <GL/glew.h>
#include <GL/glut.h>
#include <GL/glu.h>
#include <stdio.h>
#include<vector>
#include<list>
#include <fstream>
#include <sstream>
#include <iostream>
#include <string>

#define INTERNAL

//instance Attribute
/*
TransformMatrix(4*4)
Color(4)
 */
typedef struct InstAttrib_s{
   float TransfMat[16];
   float Color[4];
   bool  enable;
}InstAttrib;


const unsigned int MAX_NUM = 500;

static GLfloat vertices[] = {
   -1.0, -1.0, -1.0,   //0.left-bottom-back
   -1.0, -1.0,  1.0,   //1.left-bottom-front
   -1.0,  1.0,  1.0,   //2.left-top-front
   -1.0,  1.0, -1.0,   //3.left-top-back
    1.0, -1.0, -1.0,   //4.right-bottom-back
    1.0,  1.0, -1.0,   //5.right-top-back
    1.0,  1.0,  1.0,   //6.right-top-front
    1.0, -1.0,  1.0,   //7.right-bottom-front
   
};

static GLint indices[] = {
   0, 1, 2, 3, // left-face
   1, 7 ,6, 2, // front-face
   4, 5, 6, 7, // right-face
   4, 0, 3, 5, // back-face
   0 ,4, 7, 1, // bottom-face
   2, 6, 5, 3, // top-face
};

void init(void);
void reshape(int w, int h);
void keyPressed(unsigned char key, int x, int y);
void display(void);
void drawCubic(void);
GLuint SetShaders(void);


bool CheckShaderCompile(GLuint shader, std::string& error);
bool CheckShaderLink(GLuint program, std::string& error);
std::string readASCIIFile(const char* file);
GLuint CompileShader(const char* shaderName, GLenum shaderType);
GLuint LinkShaders(const std::vector<GLuint>& shaderVector);

void SetBuffer(void);


void init(void)
{
   glClearColor(0.0, 0.0, 0.0, 1.0);
   glShadeModel(GL_SMOOTH);

   //check opengl version
#ifdef INTERNAL
   const char* version = (const char*)glGetString(GL_VERSION);
   printf("GL VERSION : %s\n", version);
#endif
   
   //check glew for opengl2.0
   glewInit();
   if(glewIsSupported("GL_VERSION_2_0"))
   {
      printf("yes! glew support version2.0\n");
   }
   else
   {
      printf("no! it doesn't support version2.0\n");
   }

   glEnableClientState(GL_VERTEX_ARRAY);
   glVertexPointer(3, GL_FLOAT, 0, vertices);

//   glEnable(GL_DEPTH_TEST);
//   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
   //  glEnable(GL_BLEND);

}


void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei)w, (GLsizei)h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective(45, (double)w/(double)h, 0.1, 5000);
   glMatrixMode(GL_MODELVIEW);
}


void keyPressed(unsigned char key, int x, int y)
{
   
}

void draw(GLuint* indices)
{
   glDrawElementsInstanced(GL_QUADS, 24, GL_UNSIGNED_INT, indices, 2);
}




void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

   glLoadIdentity();

  gluLookAt(-10,10,10, 0,0,0, 0,1,0);
   // glBegin(GL_LINES);
   // glVertex2f(0.1, 0.1);
   // glVertex2f(0.9, 0.9);
   // glEnd();
   //   glColor3f(0.0, 1.0, 0.0);
   //drawCubic();

//   glColor3f(0.0, 1.0, 0.0);
   //glTranslatef(-10.0, 0.0, 0.0);
   //drawCubic();

   // GLint* indices = new GLint[vertIdxList.size()];
   // std::list<GLuint>::iterator itr = vertIdxList.begin();

   // for(int i=0; itr!=vertIdxList.end(); itr++, i++)
   // {
   //    indices[i] = *itr;
   // }

   glDrawElementsInstanced(GL_QUADS,24, GL_UNSIGNED_INT, indices,3);

//   glDrawElements(GL_QUADS,24, GL_UNSIGNED_INT, indices);

   
   glutSwapBuffers();
//   glutPostRedisplay();  --Here it is!!!
}


std::string readASCIIFile(const char* file)
{
   std::ifstream ifs(file);
   if(ifs.is_open())
   {
      std::stringstream ss;
      ss<<ifs.rdbuf();
      return ss.str();
   }

   return "";
}

bool CheckShaderCompile(GLuint shader, std::string& error)
{
   GLint sta;
   glGetShaderiv(shader, GL_COMPILE_STATUS, &sta);
   if(GL_FALSE == sta)
   {
      GLint maxlength=0;
      GLint infolength=0;
      glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxlength);
      char log[maxlength];
      glGetShaderInfoLog(shader, maxlength, &infolength, log);
      error = log;
      return false;
   }
   return true;
}

bool CheckShaderLink(GLuint program, std::string& error)
{
   GLint sta;
   glGetShaderiv(program, GL_LINK_STATUS, &sta);
   if(GL_FALSE == sta)
   {
      GLint maxlength=0;
      GLint infolength=0;
      glGetShaderiv(program, GL_INFO_LOG_LENGTH, &maxlength);
      char log[maxlength];
      glGetShaderInfoLog(program, maxlength, &infolength, log);
      error = log;
      return false;
   }
   return true;
}



GLuint CompileShader(const char* shaderName, GLenum shaderType)
{
   GLuint shader = glCreateShader(shaderType);
   std::string source = readASCIIFile(shaderName);
   std::string error("");
   if(source.compare(""))
   {
      const char* cstrSource = source.c_str();
      glShaderSource(shader, 1, &cstrSource, NULL);
      glCompileShader(shader);
      CheckShaderCompile(shader, error );
   }
   else
   {
      error = "Empty Shader Source";
   }

   if(error.compare(""))
   {
      std::cout<<"Compile Shader Error:"<<error.c_str()<<std::endl;
   }

   return shader;
}

GLuint LinkShaders(const std::vector<GLuint>& shaderVector)
{
   GLuint program = glCreateProgram();
   
   std::vector<GLuint>::const_iterator it=shaderVector.begin();
   for(; it!=shaderVector.end(); it++)
   {
      glAttachShader(program, *it);
   }

   glLinkProgram(program);

   std::string error("");
   CheckShaderLink(program, error);
   
   if(error.compare(""))
   {
      std::cout<<"Link Shader Error:"<<error.c_str()<<std::endl;
   }
   return program;
}

void RunShaderProgram(GLuint program)
{
   glUseProgram(program);
}


GLuint SetShaders(void)
{
   GLuint vs =CompileShader("myinstance.glsl", GL_VERTEX_SHADER);
   
   GLuint fs = CompileShader("mypixel.glsl", GL_FRAGMENT_SHADER);
   
   std::vector<GLuint> shaderVector;
   shaderVector.push_back(vs);
   shaderVector.push_back(fs);

   GLuint sp = LinkShaders(shaderVector);

   RunShaderProgram(sp);
   
   return sp;
}

bool InitInstAtrribArray(InstAttrib* iaArray, unsigned int size)
{
   int boolByte = sizeof(bool);
   int floatByte = sizeof(float);
   
   if(iaArray)
   {
      glLoadIdentity();
      for(int i=0; i<size; i++)
      {
	 
	 float transfMat[16];

	 glTranslatef(1, 0, 0);

	 glGetFloatv(GL_MODELVIEW_MATRIX, transfMat);
   

	 float color[4] = {
	    0.0f, 0.0f, 0.0f, 1.0f
	 };

	 color[i] = 1.0;
//	 color[3] = 0.3;
	 //color[3] = i;
	 
	 for(int j=0; j<16; j++)
         {
	    iaArray[i].TransfMat[j] = transfMat[j];
	 }

         for(int j=0; j<4; j++)
	 {
	    iaArray[i].Color[j] = color[j];
         }
      }
      
      iaArray[0].enable = true;
      iaArray[1].enable = false;
      iaArray[2].enable = true;

      glLoadIdentity();
      
      return true;
   }
   return false;
}


GLuint NewInstAttribBuffer(InstAttrib* iaArray, unsigned int size)
{
   GLuint iabID;	//Instance Attribute Buffer ID
   glGenBuffers(1, &iabID);
   if(glIsBuffer(iabID))
   {
      glBindBuffer(GL_ARRAY_BUFFER, iabID);
      glBufferData(GL_ARRAY_BUFFER, size*sizeof(InstAttrib), iaArray, GL_STATIC_DRAW);
   }

   glBindBuffer(GL_ARRAY_BUFFER, 0);

   return iabID;
}



void PushInstAttribBufToShader(GLuint sp, GLuint iabID)
{
   //transMat
   GLint tm_pos1 = glGetAttribLocation(sp, "transMat");
//   GLint tm_pos1 = tm_pos+0;
   GLint tm_pos2 = tm_pos1+1;
   GLint tm_pos3 = tm_pos1+2;
   GLint tm_pos4 = tm_pos1+3;

   glEnableVertexAttribArray(tm_pos1);
   glEnableVertexAttribArray(tm_pos2);
   glEnableVertexAttribArray(tm_pos3);
   glEnableVertexAttribArray(tm_pos4);

    //Color
   GLint color_pos = glGetAttribLocation(sp, "color");
   glEnableVertexAttribArray(color_pos);

   //Enable
   GLint enable_pos = glGetAttribLocation(sp, "enable");
   glEnableVertexAttribArray(enable_pos);

    //stride = transMat(16*float) + color(4*float)
   GLsizei stride =  sizeof(InstAttrib) ;

    glBindBuffer(GL_ARRAY_BUFFER, iabID);


   glVertexAttribPointer(tm_pos1, 4, GL_FLOAT, GL_FALSE, stride, (void*)(0));
   glVertexAttribPointer(tm_pos2, 4, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof(float) * 4));
   glVertexAttribPointer(tm_pos3, 4, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof(float) * 8));
   glVertexAttribPointer(tm_pos4, 4, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof(float) * 12));
   glVertexAttribPointer(color_pos, 4, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof(float) * 16));
   glVertexAttribPointer(enable_pos, 1, GL_BYTE, GL_FALSE, stride, (void*)(sizeof(float) * 20 ));

   glVertexAttribDivisor(tm_pos1, 1);
   glVertexAttribDivisor(tm_pos2, 1);
   glVertexAttribDivisor(tm_pos3, 1);
   glVertexAttribDivisor(tm_pos4, 1);
   glVertexAttribDivisor(color_pos, 1);
   glVertexAttribDivisor(enable_pos, 1);

   glBindBuffer(GL_ARRAY_BUFFER, 0);

   // glDisableVertexAttribArray(tm_pos1);
   //  glDisableVertexAttribArray(tm_pos2);
   //  glDisableVertexAttribArray(tm_pos3);
   //  glDisableVertexAttribArray(tm_pos4);
   // glDisableVertexAttribArray(color_pos);
}


   
int main(int argc, char** argv)
{
   // NewSpace(1.0f, 2, 2, 2, vertVector, cubeVector);
   // AttachCubeToDrawList(cubeVector[0], vertIdxList);
   // AttachCubeToDrawList(cubeVector[7], vertIdxList);

   // int elemNums = 3*vertVector.size();
   // float* vertArray = new float[elemNums];
   // for(int i=0; i<elemNums; i+=3)
   // {
   //    vertArray[i]   = vertVector[i/3]._x;
   //    vertArray[i+1] = vertVector[i/3]._y;
   //    vertArray[i+2] = vertVector[i/3]._z;
   // }

   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGB);
   glutInitWindowSize(800, 600);
   glutInitWindowPosition(0, 0);
   glutCreateWindow("MyCubicPixel");
   init();

   GLuint sp = SetShaders();


   InstAttrib iaArray[3];
   
    InitInstAtrribArray(iaArray, 3);

    GLuint iabID = NewInstAttribBuffer(iaArray, 3);


     if(iabID!=0)
     {
	PushInstAttribBufToShader(sp, iabID);
     }

   
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyPressed);
   glutMainLoop();
   return 0;

}






// bool CubyInit(void)
// {
//    GLBufferManager* oglBufMgr = GLBufferManager::CreateManager();
//    if(!oglBufMgr) return false;
   
//    GLShaderManager* oglShaderMgr = GLShaderManger::CreateManager(); 
//    if(!oglShaderMgr) return false;
   
//    CubeInstanceManager* cubeInstMgr =  CubeInstanceManager::CreateManager();
//    if(!cubeInstMgr) return false;

//    //prepare for Cube World
//    CubeWorld* cubeWorld = new CubeWorld(2, 10, 10, 10);

//    //prepare for Cube Instance
//    if(!cubeInstMgr->PullCubeInstFrom(cubeWorld)) {
//       return false;
//    }

//    //prepare for GL Shader
//    oglShaderMgr->CompileShader("myinstance.glsl", GL_VERTEX_SHADER);
//    oglShaderMgr->CompileShader("mypixel.glsl", GL_FRAGMENT_SHADER);
//    unsigned int oglProgram = oglShaderMgr->LinkToProgram();
//    oglShaderMgr->Run();
   

//    //prepare for GL Buffer
//    unsigned int
//       oglBufID = oglBufMgr->CreateGLBuffer(
// 	 "ogl_inst_buffer",
// 	 cubeInstMgr->GetCubeInstPointer(),
// 	 cubeInstMgr->GetCubeInstByteSize());


//    //Bind CubeInstance data with GLShader and GLBuffer
//    cubeInstMgr->PushCubeInstTo(oglBufID);
   
// }









